#include "mod_perseus.h"


void
perseus_shmht_kill(server_rec *s)
{
    perseus_config * cfg = mySrvConfig(s);
    ap_log_error(APLOG_MARK,APLOG_ERR,0,0,"Perseus cleanup resources");

    if (cfg->perseus_rmm != NULL) {
        apr_rmm_destroy(cfg->perseus_rmm);
        cfg->perseus_rmm= NULL;
    }

    if (cfg->perseus_shm) {
        apr_shm_destroy(cfg->perseus_shm);
        cfg->perseus_shm = NULL;
    }

    if (cfg->pMutex) {
        apr_global_mutex_destroy(cfg->pMutex);
        cfg->pMutex = NULL;
    }

}

static void *
shmht_malloc(void *opt_param, size_t size)
{
    server_rec * base_server = (server_rec *) opt_param;
    perseus_config *c = (perseus_config *)
        ap_get_module_config(base_server->module_config, &perseus_module);
    apr_rmm_off_t off = apr_rmm_calloc(c->perseus_rmm, size);
    return apr_rmm_addr_get(c->perseus_rmm, off);
}

static void *
shmht_calloc(void *opt_param,
        size_t number, size_t size)
{
    server_rec * base_server = (server_rec *) opt_param;
    perseus_config *c = (perseus_config *)
        ap_get_module_config(base_server->module_config, &perseus_module);
    apr_rmm_off_t off = apr_rmm_calloc(c->perseus_rmm, (size*number));
    return apr_rmm_addr_get(c->perseus_rmm, off);
}

static void *
shmht_realloc(void *opt_param, void *ptr, size_t size)
{
    server_rec * base_server = (server_rec *) opt_param;
    perseus_config *c = (perseus_config *)
        ap_get_module_config(base_server->module_config, &perseus_module);
    apr_rmm_off_t off = apr_rmm_realloc(c->perseus_rmm, ptr, size);
    return apr_rmm_addr_get(c->perseus_rmm, off);
}

static void 
shmht_free(void *opt_param, void *ptr)
{
    server_rec * base_server = (server_rec *) opt_param;
    perseus_config *c = (perseus_config *)
        ap_get_module_config(base_server->module_config, &perseus_module);
    apr_rmm_off_t off = apr_rmm_offset_get(c->perseus_rmm, ptr);
    apr_rmm_free(c->perseus_rmm, off);
    return;
}

apr_status_t
perseus_shmht_init(server_rec *base_server, apr_pool_t *p)
{
    apr_status_t ret;
    perseus_config * c = mySrvConfig(base_server);
    int n;
    apr_size_t avail;
    table_t *ta;
    int ta_errno;

    /*Shared memory creation*/
    if (c->shm_filename) {
        /*Delete any existing shm segement with this name */
        apr_shm_remove(c->shm_filename,p);
    }

    ret = apr_shm_create(&(c->perseus_shm),c->shm_size,c->shm_filename, p);
    if (ret != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, ret, base_server,
                "Failed to create shared segment file '%s'", c->shm_filename );
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*Mutex creation */
    ret = apr_global_mutex_create(&c->pMutex,c->cMutexFilename, APR_LOCK_DEFAULT, p);
    if (ret != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, ret, base_server,
                "Failed to create global mutex with file'%s'", c->cMutexFilename);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

#ifdef AP_NEED_SET_MUTEX_PERMS
    ret = unixd_set_global_mutex_perms(c->pMutex);
    if( ret != APR_SUCCESS ) {
        ap_log_perror(APLOG_MARK, APLOG_CRIT, ret, p, 
                "Failed to set mutex permission!");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
#endif 

    ret = apr_rmm_init(&(c->perseus_rmm), NULL,
            apr_shm_baseaddr_get(c->perseus_shm),
            c->shm_size, p);
    if (ret != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, ret, base_server,
                "Cannot initialize rmm");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server,
            "initialize MM %pp RMM %pp",
            (void *) c->perseus_shm,(void *) c->perseus_rmm);


    /*
     * Create hash table in shared memory segment
     */
    avail = c->shm_size;
    n = (avail/2) / 1024;
    n = n < 10 ? 10 : n;
    /*
     * Passing server_rec as opt_param to table_alloc so that we can do
     * logging if required ssl_util_table. Otherwise, mc is sufficient.
     */ 
    ta = table_alloc(n, &ta_errno, 
            shmht_malloc,  
            shmht_calloc, 
            shmht_realloc, 
            shmht_free, base_server);
    if (ta == NULL) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server,
                "Cannot allocate hash table in shared memory: %s",
                table_strerror(ta_errno));
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    table_attr(ta, TABLE_FLAG_AUTO_ADJUST|TABLE_FLAG_ADJUST_DOWN);
    table_set_data_alignment(ta, sizeof(char *));
    table_clear(ta);
    c->perseus_hashtable = ta;

    /*
     * Log the done work
     */
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, 
            "Init: Created hash-table (%d buckets) "
            "in shared memory (%" APR_SIZE_T_FMT 
            " bytes) for session cache",
            n, avail);
    return APR_SUCCESS;
}

apr_status_t
perseus_shmht_store(server_rec *s, unsigned char *id, int idlen, apr_time_t expiry, char *sess)
{
    perseus_config *c = mySrvConfig(s);
    void *vp;
    int nData = strlen(sess);
    int rv;
    /* streamline session data */
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "Store begin %d ",nData);

    perseus_mutex_on(s);
    rv = table_insert_kd(c->perseus_hashtable, 
            id, idlen, NULL, sizeof(apr_time_t)+nData,
            NULL, &vp, 1);
    if (rv != TABLE_ERROR_NONE) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "Store Error!!");
        perseus_mutex_off(s);
        return APR_EGENERAL;
    }
    memcpy(vp, &expiry, sizeof(apr_time_t));
    memcpy( (char *) vp+sizeof(apr_time_t), sess, nData);
    perseus_mutex_off(s);

    /* allow the regular expiring to occur */
    perseus_shmht_expire(s);

    return APR_SUCCESS;
}

void
perseus_shmht_remove(server_rec *s, unsigned char *id, int idlen)
{
    perseus_config *c = (perseus_config *)
        ap_get_module_config(s->module_config, &perseus_module);

    /* remove value under key in table */
    perseus_mutex_on(s);
    table_delete(c->perseus_hashtable, id, idlen, NULL, NULL);
    perseus_mutex_off(s);
    return;
}

char *
perseus_shmht_retrieve(server_rec *s, unsigned char *id, int idlen)
{
    perseus_config *c = (perseus_config *)
        ap_get_module_config(s->module_config, &perseus_module);
    void *vp;
    char *sess = NULL;
    int nData;
    apr_time_t expiry;
    apr_time_t now;
    int n;
    int rv;

    /* allow the regular expiring to occur */
    perseus_shmht_expire(s);

    /* lookup key in table */
    perseus_mutex_on(s);
    rv = table_retrieve(c->perseus_hashtable,
            id, idlen, &vp, &n);
    if (rv != TABLE_ERROR_NONE) {
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "error not found in hash : %s",id);
        perseus_mutex_off(s);
        return NULL;
    }

    /* copy over the information to the SCI */
    nData = n-sizeof(apr_time_t);
    sess = (char *) malloc(nData+1);
    if (sess == NULL) {
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "error not enough memory %d",nData);
        perseus_mutex_off(s);
        return NULL;
    }
    sess[nData]='\0';
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "memory %d",nData);
    memcpy(&expiry, vp, sizeof(apr_time_t));
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "expiry time %llu",expiry);
    memcpy((void *) sess, (char *)vp+sizeof(apr_time_t), nData);
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "sess %s",sess);
    perseus_mutex_off(s);

    /* make sure the stuff is still not expired */
    now = apr_time_now();
    if (expiry <= now) {
        perseus_shmht_remove(s, id, idlen);
        return NULL;
    }
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "END retriev ");
    return sess;
}

void 
perseus_shmht_expire(server_rec *s)
{
    perseus_config *c = (perseus_config *)
        ap_get_module_config(s->module_config, &perseus_module);

    static apr_time_t tLast = 0;
    table_linear_t iterator;
    apr_time_t tExpiresAt;
    void *vpKey;
    void *vpKeyThis;
    void *vpData;
    int nKey;
    int nKeyThis;
    int nData;
    int nElements = 0;
    int nDeleted = 0;
    int bDelete;
    int rc;
    apr_time_t tNow;

    /*
     * make sure the expiration for still not-accessed session
     * cache entries is done only from time to time
     */
    tNow = apr_time_now();
    if (tNow < tLast+c->session_cache_timeout)
        return;
    tLast = tNow;

    perseus_mutex_on(s);
    if (table_first_r(c->perseus_hashtable, &iterator,
                &vpKey, &nKey, &vpData, &nData) == TABLE_ERROR_NONE) {
        do {
            bDelete = FALSE;
            nElements++;
            if (nData < (int) sizeof(apr_time_t) || vpData == NULL)
                bDelete = TRUE;
            else {
                memcpy(&tExpiresAt, vpData, sizeof(apr_time_t));
                /* 
                 * XXX : Force the record to be cleaned up. TBD (Madhu)
                 * tExpiresAt = tNow;
                 */
                if (tExpiresAt <= tNow)
                    bDelete = TRUE;
            }
            vpKeyThis = vpKey;
            nKeyThis  = nKey;
            rc = table_next_r(c->perseus_hashtable, &iterator,
                    &vpKey, &nKey, &vpData, &nData);
            if (bDelete) {
                table_delete(c->perseus_hashtable,
                        vpKeyThis, nKeyThis, NULL, NULL);
                nDeleted++;
            }
        } while (rc == TABLE_ERROR_NONE); 
        /* (vpKeyThis != vpKey) && (nKeyThis != nKey) */
    }
    perseus_mutex_off(s);
    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
            "Inter-Process Session Cache (SHMHT) Expiry: "
            "old: %d, new: %d, removed: %d",
            nElements, nElements-nDeleted, nDeleted);
    return;
}

